/*
 * Copyright (c) 2014, Linaro Ltd. All rights reserved.
 *
 * This program and the accompanying materials
 * are licensed and made available under the terms and conditions of the BSD License
 * which accompanies this distribution.  The full text of the license may be found at
 * http://opensource.org/licenses/bsd-license.php
 *
 * THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
 * WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
 */

/*
 * Theory of operation
 * -------------------
 *
 * This code parses a Flattened Device Tree binary (DTB) to find the base of
 * system RAM. It is written in assembly so that it can be executed before a
 * stack has been set up.
 *
 * To find the base of system RAM, we have to traverse the FDT to find a memory
 * node. In the context of this implementation, the first node that has a
 * device_type property with the value 'memory' and a 'reg' property is
 * acceptable, and the name of the node (memory[@xxx]) is ignored, as are any
 * other nodes that match the above constraints.
 *
 * In pseudo code, this implementation does the following:
 *
 * for each node {
 *  have_device_type = false
 *  have_reg = false
 *
 *  for each property {
 *    if property value == 'memory' {
 *      if property name == 'device_type' {
 *        have_device_type = true
 *      }
 *    } else {
 *      if property name == 'reg' {
 *        have_reg = true
 *        membase = property value[0]
 *        memsize = property value[1]
 *      }
 *    }
 *  }
 *  if have_device_type and have_reg {
 *    return membase and memsize
 *  }
 * }
 * return NOT_FOUND
 */

#define FDT_MAGIC    0xedfe0dd0

#define FDT_BEGIN_NODE    0x1
#define FDT_END_NODE    0x2
#define FDT_PROP    0x3
#define FDT_END      0x9

  xMEMSIZE  .req  x0  // recorded system RAM size
  xMEMBASE  .req  x1  // recorded system RAM base

  xLR    .req  x8  // our preserved link register
  xDTP    .req  x9  // pointer to traverse the DT structure
  xSTRTAB    .req  x10  // pointer to the DTB string table
  xMEMNODE  .req  x11  // bit field to record found properties

#define HAVE_REG    0x1
#define HAVE_DEVICE_TYPE  0x2

  .text
  .align  3
_memory:
  .asciz  "memory"
_reg:
  .asciz  "reg"
_device_type:
  .asciz  "device_type"

  /*
   * Compare strings in x4 and x5, return in w7
   */
  .align  3
strcmp:
  ldrb  w2, [x4], #1
  ldrb  w3, [x5], #1
  subs  w7, w2, w3
  cbz  w2, 0f
  cbz  w3, 0f
  beq  strcmp
0:  ret

  .globl  find_memnode
find_memnode:
  // preserve link register
  mov  xLR, x30
  mov  xDTP, x0

  /*
   * Check the DTB magic at offset 0
   */
  movz  w4, #(FDT_MAGIC & 0xffff)
  movk  w4, #(FDT_MAGIC >> 16), lsl #16
  ldr  w5, [xDTP]
  cmp  w4, w5
  bne  err_invalid_magic

  /*
   * Read the string offset and store it for later use
   */
  ldr  w4, [xDTP, #12]
  rev  w4, w4
  add  xSTRTAB, xDTP, x4

  /*
   * Read the struct offset and add it to the DT pointer
   */
  ldr  w5, [xDTP, #8]
  rev  w5, w5
  add  xDTP, xDTP, x5

  /*
   * Check current tag for FDT_BEGIN_NODE
   */
  ldr  w5, [xDTP]
  rev  w5, w5
  cmp  w5, #FDT_BEGIN_NODE
  bne  err_unexpected_begin_tag

begin_node:
  mov  xMEMNODE, #0
  add  xDTP, xDTP, #4

  /*
   * Advance xDTP past NULL terminated string
   */
0:  ldrb  w4, [xDTP], #1
  cbnz  w4, 0b

next_tag:
  /*
   * Align the DT pointer xDTP to the next 32-bit boundary
   */
  add  xDTP, xDTP, #3
  and  xDTP, xDTP, #~3

  /*
   * Read the next tag, could be BEGIN_NODE, END_NODE, PROP, END
   */
  ldr  w5, [xDTP]
  rev  w5, w5
  cmp  w5, #FDT_BEGIN_NODE
  beq  begin_node
  cmp  w5, #FDT_END_NODE
  beq  end_node
  cmp  w5, #FDT_PROP
  beq  prop_node
  cmp  w5, #FDT_END
  beq  err_end_of_fdt
  b  err_unexpected_tag

prop_node:
  /*
   * If propname == 'reg', record as membase and memsize
   * If propname == 'device_type' and value == 'memory',
   * set the 'is_memnode' flag for this node
   */
  ldr  w6, [xDTP, #4]
  add  xDTP, xDTP, #12
  rev  w6, w6
  mov  x5, xDTP
  adr  x4, _memory
  bl  strcmp

  /*
   * Get handle to property name
   */
  ldr  w5, [xDTP, #-4]
  rev  w5, w5
  add  x5, xSTRTAB, x5

  cbz  w7, check_device_type

  /*
   * Check for 'reg' property
   */
  adr  x4, _reg
  bl  strcmp
  cbnz  w7, inc_and_next_tag

  /*
   * Extract two 64-bit quantities from the 'reg' property. These values
   * will only be used if the node also turns out to have a device_type
   * property with a value of 'memory'.
   *
   * NOTE: xDTP is only guaranteed to be 32 bit aligned, and we are most
   *       likely executing with the MMU off, so we cannot use 64 bit
   *       wide accesses here.
   */
  ldp  w4, w5, [xDTP]
  orr  xMEMBASE, x4, x5, lsl #32
  ldp  w4, w5, [xDTP, #8]
  orr  xMEMSIZE, x4, x5, lsl #32
  rev  xMEMBASE, xMEMBASE
  rev  xMEMSIZE, xMEMSIZE
  orr  xMEMNODE, xMEMNODE, #HAVE_REG
  b  inc_and_next_tag

check_device_type:
  /*
   * Check whether the current property's name is 'device_type'
   */
  adr  x4, _device_type
  bl  strcmp
  cbnz  w7, inc_and_next_tag
  orr  xMEMNODE, xMEMNODE, #HAVE_DEVICE_TYPE

inc_and_next_tag:
  add  xDTP, xDTP, x6
  b  next_tag

end_node:
  /*
   * Check for device_type = memory and reg = xxxx
   * If we have both, we are done
   */
  add  xDTP, xDTP, #4
  cmp  xMEMNODE, #(HAVE_REG | HAVE_DEVICE_TYPE)
  bne  next_tag

  ret  xLR

err_invalid_magic:
err_unexpected_begin_tag:
err_unexpected_tag:
err_end_of_fdt:
  wfi
